/*
 * Copyright 2019 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.ohos.proteus.value;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentState;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.StateElement;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.utils.Pair;

import com.flipkart.ohos.proteus.ProteusLayoutInflater;
import com.flipkart.ohos.proteus.ProteusView;
import com.flipkart.ohos.proteus.exceptions.ProteusInflateException;
import com.flipkart.ohos.proteus.parser.ParseHelper;
import com.flipkart.ohos.proteus.processor.ColorResourceProcessor;
import com.flipkart.ohos.proteus.processor.DimensionAttributeProcessor;
import com.flipkart.ohos.proteus.processor.DrawableResourceProcessor;
import com.flipkart.ohos.proteus.toolbox.SimpleArrayIterator;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * ElementValue
 *
 * @author aditya.sharat
 */
public abstract class ElementValue extends Value {
    private static final String TYPE = "type";
    private static final String CHILDREN = "children";

    private static final String ELEMENT_SELECTOR = "selector";
    private static final String ELEMENT_SHAPE = "shape";
    private static final String ELEMENT_LAYER_LIST = "layer-list";
    private static final String ELEMENT_LEVEL_LIST = "level-list";
    private static final String ELEMENT_RIPPLE = "ripple";
    private static final String ELEMENT_IMAGE = "image";

    private static final String TYPE_CORNERS = "corners";
    private static final String TYPE_GRADIENT = "gradient";
    private static final String TYPE_PADDING = "padding";
    private static final String TYPE_SIZE = "size";
    private static final String TYPE_SOLID = "solid";
    private static final String TYPE_STROKE = "stroke";

    public static ElementValue valueOf(String value, Context context) {
        if (Color.isColor(value)) {
            return ColorValue.valueOf(value);
        } else {
            return UrlValue.valueOf(value);
        }
    }

    public static ElementValue valueOf(ObjectValue value, Context context) {
        String type = value.getAsString(TYPE);
        //noinspection ConstantConditions
        switch (type) {
            case ELEMENT_SELECTOR:
                return StateListValue.valueOf(value.getAsArray(CHILDREN), context);
            case ELEMENT_SHAPE:
                return ShapeValue.valueOf(value, context);
            case ELEMENT_LAYER_LIST:
                return LayerListValue.valueOf(value.getAsArray(CHILDREN), context);
            case ELEMENT_LEVEL_LIST:
                return LevelListValue.valueOf(value.getAsArray(CHILDREN), context);
            case ELEMENT_RIPPLE:
                return RippleValue.valueOf(value, context);
            case ELEMENT_IMAGE:
                return RawFileValue.valueOf(value);
            default:
                return null;
        }
    }

    public static Element convertBitmapToElement(PixelMap pixelMap, Context context) {
        return new PixelMapElement(pixelMap);
    }

    public abstract void apply(ProteusView view, Context context,
                               ProteusLayoutInflater.ImageLoader loader, Callback callback);

    @Override
    public Value copy() {
        return this;
    }

    public interface Callback {
        void apply(Element element);
    }

    public static class ColorValue extends ElementValue {
        public static final ColorValue BLACK = new ColorValue(Color.Int.BLACK);

        public final Value color;

        private ColorValue(Value color) {
            this.color = color;
        }

        public static ColorValue valueOf(String value) {
            return new ColorValue(Color.valueOf(value));
        }

        public static ColorValue valueOf(Value value, Context context) {
            return new ColorValue(ColorResourceProcessor.staticCompile(value, context));
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(ColorResourceProcessor.evaluate(color, view).colors));
            callback.apply(shapeElement);
        }
    }

    public static class ShapeValue extends ElementValue {
        private static final int SHAPE_NONE = -1;

        private static final String SHAPE_RECTANGLE = "rectangle";
        private static final String SHAPE_OVAL = "oval";
        private static final String SHAPE_LINE = "line";
        private static final String SHAPE_RING = "ring";

        private static final String SHAPE = "shape";
        private static final int DEFAULT_SHAPE = -1;

        public final int shape;

        public final Gradient gradient;

        private final ElementData[] elements;

        private ShapeValue(ObjectValue value, Context context) {
            this.shape = getShape(value.getAsString(SHAPE));

            Gradient gradient = null;
            Array children = value.getAsArray(CHILDREN);
            Iterator<Value> iterator = children.iterator();

            if (children.size() > 0) {
                this.elements = new ElementData[children.size()];
                int index = 0;
                while (iterator.hasNext()) {
                    ObjectValue child = iterator.next().getAsObject();
                    String typeKey = child.getAsString(TYPE);
                    ElementData element = null;
                    //noinspection ConstantConditions
                    switch (typeKey) {
                        case TYPE_CORNERS:
                            element = Corners.valueOf(child, context);
                            break;
                        case TYPE_PADDING:
                            break;
                        case TYPE_SIZE:
                            element = Size.valueOf(child, context);
                            break;
                        case TYPE_SOLID:
                            element = Solid.valueOf(child, context);
                            break;
                        case TYPE_STROKE:
                            element = Stroke.valueOf(child, context);
                            break;
                        case TYPE_GRADIENT:
                            gradient = Gradient.valueOf(child, context);
                            element = gradient;
                            break;
                    }
                    if (null != element) {
                        this.elements[index] = element;
                        index++;
                    }
                }
                this.gradient = gradient;
            } else {
                this.elements = null;
                this.gradient = null;
            }
        }

        private ShapeValue(int shape, Gradient gradient, ElementData[] elements) {
            this.shape = shape;
            this.gradient = gradient;
            this.elements = elements;
        }

        public static ShapeValue valueOf(ObjectValue value, Context context) {
            return new ShapeValue(value, context);
        }

        public static ShapeValue valueOf(int shape, Gradient gradient, ElementData[] elements) {
            return new ShapeValue(shape, gradient, elements);
        }

        private static int getShape(String shape) {
            switch (shape) {
                case SHAPE_RECTANGLE:
                    return ShapeElement.RECTANGLE;
                case SHAPE_OVAL:
                    return ShapeElement.OVAL;
                case SHAPE_LINE:
                    return ShapeElement.LINE;
                case SHAPE_RING:
                    return ShapeElement.ARC;
                default:
                    return SHAPE_NONE;
            }
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            ShapeElement shapeElement = null != gradient ? gradient.init(view) : new ShapeElement();
            if (DEFAULT_SHAPE != shape) {
                shapeElement.setShape(shape);
            }
            if (null != elements) {
                for (ElementData element : elements) {
                    element.apply(view, shapeElement);
                }
            }
            callback.apply(shapeElement);
        }
    }

    public static class LayerListValue extends ElementValue {
        private static final String ID_STR = "id";
        private static final String ELEMENT_STR = "Element";

        private final int[] ids;
        private final Value[] layers;

        private LayerListValue(Array layers, Context context) {
            this.ids = new int[layers.size()];
            this.layers = new Value[layers.size()];
            Pair<Integer, Value> pair;
            int index = 0;
            Iterator<Value> iterator = layers.iterator();
            while (iterator.hasNext()) {
                pair = parseLayer(iterator.next().getAsObject(), context);
                this.ids[index] = pair.f;
                this.layers[index] = pair.s;
                index++;
            }
        }

        public LayerListValue(int[] ids, Value[] layers) {
            this.ids = ids;
            this.layers = layers;
        }

        public static LayerListValue valueOf(Array layers, Context context) {
            return new LayerListValue(layers, context);
        }

        public static LayerListValue valueOf(int[] ids, Value[] layers) {
            return new LayerListValue(ids, layers);
        }

        public static Pair<Integer, Value> parseLayer(ObjectValue layer, Context context) {
            String id = layer.getAsString(ID_STR);
            int resId = Component.ID_DEFAULT;
            if (id != null) {
                resId = ParseHelper.getOhosXmlResId(id);
            }
            Value element = layer.get(ELEMENT_STR);
            Value out = DrawableResourceProcessor.staticCompile(element, context);
            return new Pair<>(resId, out);
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            final Element[] elements = new Element[layers.length];
            int index = 0;
            for (Value layer : layers) {
                elements[index] = DrawableResourceProcessor.evaluate(layer, view);
                index++;
            }
            //LayerElement layerElement = new LayerElement(Elements);

            // we could have avoided the following loop if layer Element
            // had a method to add Element and set id at same time
            for (int i = 0; i < elements.length; i++) {
                //layerElement.setId(i, ids[i]);
            }
            //callback.apply(layerElement);
        }

        public Iterator<Integer> getIds() {
            return SimpleArrayIterator.createIntArrayIterator(ids);
        }

        public Iterator<Value> getLayers() {
            return new SimpleArrayIterator<>(layers);
        }
    }

    public static class StateListValue extends ElementValue {
        private static final Map<String, Integer> sStateMap = new HashMap<>();

        static {
            sStateMap.put("state_pressed", ComponentState.COMPONENT_STATE_PRESSED);
            sStateMap.put("state_empty", ComponentState.COMPONENT_STATE_EMPTY);
            sStateMap.put("state_focused", ComponentState.COMPONENT_STATE_FOCUSED);
            sStateMap.put("state_hovered", ComponentState.COMPONENT_STATE_HOVERED);
            sStateMap.put("state_selected", ComponentState.COMPONENT_STATE_SELECTED);
            sStateMap.put("state_checked", ComponentState.COMPONENT_STATE_CHECKED);
        }

        public final int[][] states;
        private final Value[] values;

        private StateListValue(int[][] states, Value[] values) {
            this.states = states;
            this.values = values;
        }

        private StateListValue(Array states, Context context) {
            this.states = new int[states.size()][states.size()];
            this.values = new Value[states.size()];
            Iterator<Value> iterator = states.iterator();
            Pair<Integer, Value[]> pair;
            int index = 0;
            while (iterator.hasNext()) {
                pair = parseState(states, iterator.next().getAsObject(), context, index);
                this.states[index][0] = pair.f;
                this.values[index] = pair.s[index];
                index++;
            }
        }

        public static StateListValue valueOf(int[][] states, Value[] values) {
            return new StateListValue(states, values);
        }

        public static StateListValue valueOf(Array states, Context context) {
            return new StateListValue(states, context);
        }

        private static Pair<Integer, Value[]> parseState(Array states, ObjectValue value, Context context, int index) {
            Value[] element = new Value[states.size()];
            int state = 0;
            for (Map.Entry<String, Value> entry : value.getAsObject().entrySet()) {
                Integer stateInteger = sStateMap.get(entry.getKey());
                if (stateInteger != null) {
                    state = stateInteger;
                    element[index] = DrawableResourceProcessor.staticCompile(value.get(entry.getKey()), context);
                } else {
                    throw new IllegalArgumentException(entry.getKey() + " is not a valid state");
                }
            }
            return new Pair<>(state, element);
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            final StateElement stateListElement = new StateElement();
            int size = states.length;
            for (int i = 0; i < size; i++) {
                stateListElement.addState(states[i], DrawableResourceProcessor.evaluate(values[i], view));
            }
            callback.apply(stateListElement);
        }

        public Iterator<Value> getValues() {
            return new SimpleArrayIterator<>(values);
        }
    }

    public static class LevelListValue extends ElementValue {
        private final Level[] levels;

        private LevelListValue(Array levels, Context context) {
            this.levels = new Level[levels.size()];
            int index = 0;
            Iterator<Value> iterator = levels.iterator();
            while (iterator.hasNext()) {
                this.levels[index] = new Level(iterator.next().getAsObject(), context);
                index++;
            }
        }

        private LevelListValue(Level[] levels) {
            this.levels = levels;
        }

        public static LevelListValue valueOf(Array layers, Context context) {
            return new LevelListValue(layers, context);
        }

        public static LevelListValue value(Level[] levels) {
            return new LevelListValue(levels);
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            //final LevelListElement levelListElement = new LevelListElement();
            //for (Level level : levels) {
            //    level.apply(view, levelListElement);
            //}
        }

        public Iterator<Level> getLevels() {
            return new SimpleArrayIterator<>(levels);
        }

        public static class Level {
            public static final String MIN_LEVEL = "minLevel";
            public static final String MAX_LEVEL = "maxLevel";
            public static final String ELEMENT = "Element";

            public final int minLevel;
            public final int maxLevel;

            public final Value element;

            Level(ObjectValue value, Context context) {
                //noinspection ConstantConditions
                minLevel = value.getAsInteger(MIN_LEVEL);

                //noinspection ConstantConditions
                maxLevel = value.getAsInteger(MAX_LEVEL);

                element = DrawableResourceProcessor.staticCompile(value.get(ELEMENT), context);
            }

            private Level(int minLevel, int maxLevel, Value element) {
                this.minLevel = minLevel;
                this.maxLevel = maxLevel;
                this.element = element;
            }

            public static Level valueOf(int minLevel, int maxLevel, Value element, Context context) {
                return new Level(minLevel, maxLevel, DrawableResourceProcessor.staticCompile(element, context));
            }

            //public void apply(ProteusView view, final LevelListElement levelListElement) {
            //    levelListElement.addLevel(minLevel, maxLevel, DrawableResourceProcessor.evaluate(element, view));
            //}
        }
    }

    public static class RippleValue extends ElementValue {
        public static final String COLOR = "color";
        public static final String MASK = "mask";
        public static final String CONTENT = "content";
        public static final String DEFAULT_BACKGROUND = "defaultBackground";

        public final Value color;

        public final Value mask;

        public final Value content;

        public final Value defaultBackground;

        public RippleValue(Value color, Value mask, Value content, Value defaultBackground) {
            this.color = color;
            this.mask = mask;
            this.content = content;
            this.defaultBackground = defaultBackground;
        }

        private RippleValue(ObjectValue object, Context context) {
            color = object.get(COLOR);
            mask = DrawableResourceProcessor.staticCompile(object.get(MASK), context);
            content = DrawableResourceProcessor.staticCompile(object.get(CONTENT), context);
            defaultBackground = DrawableResourceProcessor.staticCompile(object.get(DEFAULT_BACKGROUND), context);
        }

        public static RippleValue valueOf(Value color, Value mask, Value content, Value defaultBackground) {
            return new RippleValue(color, mask, content, defaultBackground);
        }

        public static RippleValue valueOf(ObjectValue value, Context context) {
            return new RippleValue(value, context);
        }

        @Override
        public void apply(ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, Callback callback) {
            int colors;
            Element contentElement = null;
            Element maskElement = null;
            Element resultElement = null;

            Color.Result result = ColorResourceProcessor.evaluate(color, view);

            if (0 != result.colors) {
                colors = result.colors;
            } else {
            }
        }
    }

    public static class RawFileValue extends ElementValue {
        public final String stringUrl;
        public final String size;
        public final Value value;

        private RawFileValue(ObjectValue value) {
            String elementUrl = "src";
            String checkSize = "checkSize";
            this.value = value;
            this.stringUrl = value.getAsString(elementUrl);
            this.size = value.getAsString(checkSize);
        }

        public static RawFileValue valueOf(ObjectValue value) {
            return new RawFileValue(value);
        }

        @Override
        public void apply(final ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, final Callback callback) {
            loader.getSrcBitmap(view, stringUrl, size, new AsyncCallback() {
                @Override
                protected void apply(Element element) {
                    callback.apply(element);
                }

                @Override
                protected void apply(PixelMap pixelMap) {
                    callback.apply(convertBitmapToElement(pixelMap, view.getAsView().getContext()));
                }
            });
        }
    }

    public static class UrlValue extends ElementValue {
        public final String stringUrl;

        private UrlValue(String url) {
            this.stringUrl = url;
        }

        public static UrlValue valueOf(String url) {
            return new UrlValue(url);
        }

        @Override
        public void apply(final ProteusView view, Context context,
                          ProteusLayoutInflater.ImageLoader loader, final Callback callback) {
            loader.getUrlBitmap(view, stringUrl, new AsyncCallback() {
                @Override
                protected void apply(Element element) {
                    callback.apply(element);
                }

                @Override
                protected void apply(PixelMap pixelMap) {
                    callback.apply(convertBitmapToElement(pixelMap, view.getAsView().getContext()));
                }
            });
        }
    }

    private abstract static class ElementData extends Value {
        public abstract void apply(ProteusView view, ShapeElement shapeElement);

        @Override
        public Value copy() {
            return this;
        }
    }

    public static class Gradient extends ElementData {
        public static final String ANGLE = "angle";
        public static final String CENTER_X = "centerX";
        public static final String CENTER_Y = "centerY";
        public static final String CENTER_COLOR = "centerColor";
        public static final String END_COLOR = "endColor";
        public static final String GRADIENT_RADIUS = "gradientRadius";
        public static final String RADIUS = "radius";
        public static final String START_COLOR = "startColor";
        public static final String GRADIENT_TYPE = "gradientType";
        public static final String USE_LEVEL = "useLevel";

        private static final int GRADIENT_TYPE_NONE = -1;

        private static final String LINEAR_GRADIENT = "linear";
        private static final String RADIAL_GRADIENT = "radial";
        private static final String SWEEP_GRADIENT = "sweep";

        public final Integer angle;

        public final Integer radius;

        public final Float centerX;

        public final Float centerY;

        public final Value centerColor;

        public final Value endColor;

        public final Value gradientRadius;

        public final Value startColor;

        public final int gradientType;

        public final Boolean useLevel;

        private Gradient(ObjectValue gradient, Context context) {
            angle = gradient.getAsInteger(ANGLE);
            radius = gradient.getAsInteger(RADIUS);
            centerX = gradient.getAsFloat(CENTER_X);
            centerY = gradient.getAsFloat(CENTER_Y);

            Value value;

            value = gradient.get(START_COLOR);
            if (value != null) {
                startColor = ColorResourceProcessor.staticCompile(value, context);
            } else {
                startColor = null;
            }

            value = gradient.get(CENTER_COLOR);
            if (value != null) {
                centerColor = ColorResourceProcessor.staticCompile(value, context);
            } else {
                centerColor = null;
            }

            value = gradient.get(END_COLOR);
            if (value != null) {
                endColor = ColorResourceProcessor.staticCompile(value, context);
            } else {
                endColor = null;
            }

            value = gradient.get(GRADIENT_RADIUS);
            if (value != null) {
                gradientRadius = DimensionAttributeProcessor.staticCompile(value, context);
            } else {
                gradientRadius = null;
            }

            gradientType = getGradientType(gradient.getAsString(GRADIENT_TYPE));

            useLevel = gradient.getAsBoolean(USE_LEVEL);
        }

        public static Gradient valueOf(ObjectValue value, Context context) {
            return new Gradient(value, context);
        }

        public static ShapeElement.Orientation getOrientation(Integer angle) {
            ShapeElement.Orientation orientation = ShapeElement.Orientation.LEFT_TO_RIGHT;
            if (null != angle) {
                angle %= 360;
                if (angle % 45 == 0) {
                    switch (angle) {
                        case 0:
                            orientation = ShapeElement.Orientation.LEFT_TO_RIGHT;
                            break;
                        case 45:
                            orientation = ShapeElement.Orientation.BOTTOM_LEFT_TO_TOP_RIGHT;
                            break;
                        case 90:
                            orientation = ShapeElement.Orientation.BOTTOM_TO_TOP;
                            break;
                        case 135:
                            orientation = ShapeElement.Orientation.BOTTOM_RIGHT_TO_TOP_LEFT;
                            break;
                        case 180:
                            orientation = ShapeElement.Orientation.RIGHT_TO_LEFT;
                            break;
                        case 225:
                            orientation = ShapeElement.Orientation.TOP_RIGHT_TO_BOTTOM_LEFT;
                            break;
                        case 270:
                            orientation = ShapeElement.Orientation.TOP_TO_BOTTOM;
                            break;
                        case 315:
                            orientation = ShapeElement.Orientation.TOP_LEFT_TO_BOTTOM_RIGHT;
                            break;
                    }
                }
            }
            return orientation;
        }

        public static ShapeElement init(int[] colors, Integer angle, Integer radius) {
            ShapeElement shapeElement = new ShapeElement();
            if (angle != null) {
                shapeElement.setOrientation(getOrientation(angle));
            }
            if (radius != null) {
                shapeElement.setCornerRadius(radius);
            }
            if (colors.length == 0) {
                return shapeElement;
            } else if (colors.length == 1) {
                shapeElement.setRgbColors(new RgbColor[]{RgbColor.fromArgbInt(colors[0])});
            } else if (colors.length == 2) {
                shapeElement.setRgbColors(new RgbColor[]{RgbColor.fromArgbInt(colors[0]),
                    RgbColor.fromArgbInt(colors[1])});
            } else if (colors.length == 3) {
                shapeElement.setRgbColors(new RgbColor[]{RgbColor.fromArgbInt(colors[0]),
                    RgbColor.fromArgbInt(colors[1]),
                    RgbColor.fromArgbInt(colors[2])});
            }
            return shapeElement;
        }

        @Override
        public void apply(ProteusView view, final ShapeElement shapeElement) {
            if (GRADIENT_TYPE_NONE != gradientType) {
                shapeElement.setShaderType(gradientType);
            }
        }

        private int getGradientType(String type) {
            if (null == type) {
                return GRADIENT_TYPE_NONE;
            }
            switch (type) {
                case LINEAR_GRADIENT:
                    return ShapeElement.LINEAR_GRADIENT_SHADER_TYPE;
                case RADIAL_GRADIENT:
                    return ShapeElement.RADIAL_GRADIENT_SHADER_TYPE;
                case SWEEP_GRADIENT:
                    return ShapeElement.SWEEP_GRADIENT_SHADER_TYPE;
                default:
                    return GRADIENT_TYPE_NONE;
            }
        }

        public ShapeElement init(ProteusView view) {
            int[] element;
            if (centerColor != null) {
                element = new int[3];
                element[0] = ColorResourceProcessor.evaluate(startColor, view).colors;
                element[1] = ColorResourceProcessor.evaluate(centerColor, view).colors;
                element[2] = ColorResourceProcessor.evaluate(endColor, view).colors;
            } else {
                element = new int[2];
                element[0] = ColorResourceProcessor.evaluate(startColor, view).colors;
                element[1] = ColorResourceProcessor.evaluate(endColor, view).colors;
            }
            return init(element, angle, radius);
        }
    }

    public static class Corners extends ElementData {
        public static final String RADIUS = "radius";
        public static final String TOP_LEFT_RADIUS = "topLeftRadius";
        public static final String TOP_RIGHT_RADIUS = "topRightRadius";
        public static final String BOTTOM_LEFT_RADIUS = "bottomLeftRadius";
        public static final String BOTTOM_RIGHT_RADIUS = "bottomRightRadius";

        private final Value radius;

        private final Value topLeftRadius;

        private final Value topRightRadius;

        private final Value bottomLeftRadius;

        private final Value bottomRightRadius;

        private Corners(ObjectValue corner, Context context) {
            radius = DimensionAttributeProcessor.staticCompile(corner.get(RADIUS), context);
            topLeftRadius = DimensionAttributeProcessor.staticCompile(corner.get(TOP_LEFT_RADIUS), context);
            topRightRadius = DimensionAttributeProcessor.staticCompile(corner.get(TOP_RIGHT_RADIUS), context);
            bottomLeftRadius = DimensionAttributeProcessor.staticCompile(corner.get(BOTTOM_LEFT_RADIUS), context);
            bottomRightRadius = DimensionAttributeProcessor.staticCompile(corner.get(BOTTOM_RIGHT_RADIUS), context);
        }

        public static Corners valueOf(ObjectValue corner, Context context) {
            return new Corners(corner, context);
        }

        @Override
        public void apply(ProteusView view, ShapeElement shapeElement) {
            if (null != radius) {
                shapeElement.setCornerRadius(DimensionAttributeProcessor.evaluate(radius, view));
            }
            float fTopLeftRadius = DimensionAttributeProcessor.evaluate(topLeftRadius, view);
            float fTopRightRadius = DimensionAttributeProcessor.evaluate(topRightRadius, view);
            float fBottomRightRadius = DimensionAttributeProcessor.evaluate(bottomRightRadius, view);
            float fBottomLeftRadius = DimensionAttributeProcessor.evaluate(bottomLeftRadius, view);

            if (fTopLeftRadius != 0 || fTopRightRadius != 0 || fBottomRightRadius != 0 || fBottomLeftRadius != 0) {
                // The corner radii are specified in clockwise order (see Path.addRoundRect())
                shapeElement.setCornerRadiiArray(new float[]{
                    fTopLeftRadius, fTopLeftRadius,
                    fTopRightRadius, fTopRightRadius,
                    fBottomRightRadius, fBottomRightRadius,
                    fBottomLeftRadius, fBottomLeftRadius
                });
            }
        }
    }

    public static class Solid extends ElementData {
        public static final String COLOR = "color";

        private final Value color;

        private Solid(ObjectValue value, Context context) {
            color = ColorResourceProcessor.staticCompile(value.get(COLOR), context);
        }

        public static Solid valueOf(ObjectValue value, Context context) {
            return new Solid(value, context);
        }

        @Override
        public void apply(ProteusView view, final ShapeElement shapeElement) {
            Color.Result result = ColorResourceProcessor.evaluate(color, view);
            shapeElement.setRgbColor(RgbColor.fromArgbInt(result.colors));
        }
    }

    public static class Size extends ElementData {
        private static final String WIDTH = "width";
        private static final String HEIGHT = "height";

        private final Value width;
        private final Value height;

        private Size(ObjectValue value, Context context) {
            width = DimensionAttributeProcessor.staticCompile(value.get(WIDTH), context);
            height = DimensionAttributeProcessor.staticCompile(value.get(HEIGHT), context);
        }

        public static Size valueOf(ObjectValue value, Context context) {
            return new Size(value, context);
        }

        @Override
        public void apply(ProteusView view, ShapeElement shapeElement) {
            //shapeElement.setSize((int) DimensionAttributeProcessor.evaluate(width, view),
            // (int) DimensionAttributeProcessor.evaluate(height, view));
        }
    }

    public static class Stroke extends ElementData {
        public static final String WIDTH = "width";
        public static final String COLOR = "color";
        public static final String DASH_WIDTH = "dashWidth";
        public static final String DASH_GAP = "dashGap";

        public final Value width;
        public final Value color;
        public final Value dashWidth;
        public final Value dashGap;

        private Stroke(ObjectValue stroke, Context context) {
            width = DimensionAttributeProcessor.staticCompile(stroke.get(WIDTH), context);
            color = ColorResourceProcessor.staticCompile(stroke.get(COLOR), context);
            dashWidth = DimensionAttributeProcessor.staticCompile(stroke.get(DASH_WIDTH), context);
            dashGap = DimensionAttributeProcessor.staticCompile(stroke.get(DASH_GAP), context);
        }

        public static Stroke valueOf(ObjectValue stroke, Context context) {
            return new Stroke(stroke, context);
        }

        @Override
        public void apply(ProteusView view, ShapeElement shapeElement) {
            RgbColor rgbColor = new RgbColor(ColorResourceProcessor.evaluate(color, view).colors);
            shapeElement.setStroke((int) DimensionAttributeProcessor.evaluate(width, view), rgbColor);
        }
    }

    /**
     * AsyncCallback
     *
     * @author aditya.sharat
     */
    public abstract static class AsyncCallback {
        private boolean recycled;

        protected AsyncCallback() {
        }

        public void setBitmap(PixelMap pixelMap) {
            if (recycled) {
                try {
                    throw new ProteusInflateException("Cannot make calls to a recycled instance!");
                } catch (ProteusInflateException e) {
                    e.printStackTrace();
                }
            }
            apply(pixelMap);
            recycled = true;
        }

        public void setElement(Element element) {
            if (recycled) {
                try {
                    throw new ProteusInflateException("Cannot make calls to a recycled instance!");
                } catch (ProteusInflateException e) {
                    e.printStackTrace();
                }
            }
            apply(element);
            recycled = true;
        }

        protected abstract void apply(Element element);

        protected abstract void apply(PixelMap pixelMap);
    }
}

